﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TasksModel
{
    /// <summary>
    /// Root object of the content managed by TasksUI.
    /// Aggregates all information required by program.
    /// </summary>
    [System.Xml.Serialization.XmlRootAttribute(Namespace = "http://mp.tasks.com/2010/", IsNullable = false)]
    public class TaskLibrary : IdentifiedObject
    {
        #region Properties

        private TaskAuthor _createdBy;

        public TaskAuthor CreatedBy
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _createdBy; }
            set
            {
                if (_createdBy != value)
                {
                    _createdBy = value;
                    NotifyPropertyChanged("CreatedBy");
                }
            }
        }

        private DateTime _createdAt;

        /// <summary>
        /// In UTC.
        /// </summary>
        public DateTime CreatedAt
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _createdAt; }
            set
            {
                value = CutMilliseconds(value);
                if (_createdAt != value)
                {
                    _createdAt = value;
                    NotifyPropertyChanged("CreatedAt");
                }
            }
        }

        private DateTime? _changedAt;

        /// <summary>
        /// In UTC.
        /// </summary>
        public DateTime? ChangedAt
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _changedAt; }
            set
            {
                value = CutMilliseconds(value);
                if (_changedAt != value)
                {
                    _changedAt = value;
                    NotifyPropertyChanged("ChangedAt");
                }
            }
        }

        private DateTime? _lastSynchronized;

        /// <summary>
        /// In UTC.
        /// </summary>
        public DateTime? LastSynchronized
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _lastSynchronized; }
            set
            {
                value = CutMilliseconds(value);
                if (_lastSynchronized != value)
                {
                    _lastSynchronized = value;
                    NotifyPropertyChanged("LastSynchronized");
                }
            }
        }

        private TaskGroup _tasks = new TaskGroup();

        /// <summary>
        /// Do not set manually.
        /// </summary>
        public TaskGroup Tasks
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _tasks; }
            [System.Diagnostics.DebuggerStepThrough]
            set { _tasks = value; }
        }

        private string _notes;

        /// <summary>
        /// Serialized in Content property.
        /// </summary>
        [System.Xml.Serialization.XmlIgnore]
        public string Notes
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _notes; }
            set
            {
                if (!object.ReferenceEquals(_notes, value))
                {
                    _notes = value;
                    NotifyPropertyChanged("Notes");
                }
            }
        }

        /// <summary>
        /// For serialization of Notes only. Do not use manually.
        /// </summary>
        [System.Xml.Serialization.XmlElement("Notes")]
        public System.Xml.XmlLinkedNode Content
        {
            get
            {
                System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
                return doc.CreateCDataSection(_notes);
            }
            set
            {
                if (value == null)
                    _notes = null;
                else
                    _notes = value.Value;
            }
        }

        private SynchSettings _synchSettings;

        /// <summary>
        /// External synchronization configuration, if any.
        /// </summary>
        public SynchSettings SynchSettings
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _synchSettings; }
            set
            {
                if (!object.ReferenceEquals(_synchSettings, value))
                {
                    _synchSettings = value;
                    NotifyPropertyChanged("SynchSettings");
                }
            }
        }

        private TaskStatusList _statuses = new TaskStatusList();

        /// <summary>
        /// Available statuses. Do not set manually.
        /// </summary>
        public TaskStatusList Statuses
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _statuses; }
            [System.Diagnostics.DebuggerStepThrough]
            set { _statuses = value; }
        }

        private TaskAuthorList _authors = new TaskAuthorList();

        /// <summary>
        /// Available authors. Do not set manually.
        /// </summary>
        public TaskAuthorList Authors
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _authors; }
            [System.Diagnostics.DebuggerStepThrough]
            set { _authors = value; }
        }

        private TaskFlagList _flags = new TaskFlagList();

        /// <summary>
        /// Available flags. Do not set manually.
        /// </summary>
        public TaskFlagList Flags
        {
            [System.Diagnostics.DebuggerStepThrough]
            get { return _flags; }
            [System.Diagnostics.DebuggerStepThrough]
            set { _flags = value; }
        }

        /// <summary>
        /// Get version of assembly.
        /// </summary>
        public static string ApiVersion
        {
            get
            {
                System.Reflection.AssemblyFileVersionAttribute[] atl = System.Reflection.Assembly.GetExecutingAssembly().GetCustomAttributes(
                    typeof(System.Reflection.AssemblyFileVersionAttribute), false) as System.Reflection.AssemblyFileVersionAttribute[];
                return atl[0].Version;
            }
        }

        #endregion

        #region Constructors

        public TaskLibrary(Guid id)
        {
            ID = id;
        }

        public TaskLibrary()
            : this(Guid.NewGuid())
        {
        }

        #endregion

        #region Methods

        public void MergeWith(TaskLibrary tl)
        {
            Tasks.MergeWith(tl.Tasks);
            if (tl.Timestamp > Timestamp)
                if (string.Compare(Notes, tl.Notes, StringComparison.Ordinal) != 0)
                    Notes = tl.Notes;

            foreach (TaskStatus ts in tl.Statuses)
                if (null == Statuses.FirstOrDefault(delegate(TaskStatus s)
                {
                    return ts.ID == s.ID;
                }))
                    Statuses.Add(ts);
            Statuses.Sort(delegate(TaskStatus s)
            {
                return s.Name;
            });

            foreach (TaskFlag ts in tl.Flags)
                if (null == Flags.FirstOrDefault(delegate(TaskFlag s)
                {
                    return ts.ID == s.ID;
                }))
                    Flags.Add(ts);
            Flags.Sort(delegate(TaskFlag s)
            {
                return s.Name;
            });

            foreach (TaskAuthor ts in tl.Authors)
                if (null == Authors.FirstOrDefault(delegate(TaskAuthor s)
                {
                    return ts.ID == s.ID;
                }))
                    Authors.Add(ts);
            Authors.Sort(delegate(TaskAuthor s)
            {
                return s.Name;
            });

            SynchSettings = tl.SynchSettings;
            if (tl.LastSynchronized.HasValue)
                LastSynchronized = tl.LastSynchronized;
        }

        public void Clear()
        {
            Tasks.Clear();
            Statuses.Clear();
            Flags.Clear();
            Authors.Clear();
            LastSynchronized = null;
            SynchSettings = null;
            Notes = null;
            CreatedBy = null;
            CreatedAt = DateTime.MinValue;
            ChangedAt = null;
        }

        public void OptimizeReferences()
        {
            Optimize(Tasks.ImportantNotUrgentTasks);
            Optimize(Tasks.ImportantUrgentTasks);
            Optimize(Tasks.UnimportantNotUrgentTasks);
            Optimize(Tasks.UnimportantUrgentTasks);
            Optimize(Tasks.Archived);
        }

        public bool CheckAuthorUsed(TaskAuthor ta)
        {
            if (CreatedBy.ID == ta.ID)
                return true;
            return Tasks.CheckAuthorUsed(ta);
        }

        public bool CheckStatusUsed(TaskStatus ts)
        {
            return Tasks.CheckStatusUsed(ts);
        }

        public bool CheckFlagUsed(TaskFlag tf)
        {
            return Tasks.CheckFlagUsed(tf);
        }

        public TaskAuthor FindAuthorByName(string name)
        {
            return Authors.FirstOrDefault(delegate(TaskAuthor ta)
            {
                return ta.Name == name;
            });
        }

        public TaskStatus FindStatusByName(string name)
        {
            return Statuses.FirstOrDefault(delegate(TaskStatus ts)
            {
                return ts.Name == name;
            });
        }

        public TaskFlag FindFlagByName(string name)
        {
            return Flags.FirstOrDefault(delegate(TaskFlag ts)
            {
                return ts.Name == name;
            });
        }

        /// <summary>
        /// Get all tasks, except archived, that are expected to be executed.
        /// Important come before Unimportant, Urgent before Not Urgent.
        /// </summary>
        /// <param name="asForUtcDate">the NOW</param>
        /// <returns>not null</returns>
        public List<Task> GetDueTasks(DateTime asForUtcDate)
        {
            System.Diagnostics.Debug.Assert(asForUtcDate.Kind != DateTimeKind.Local);
            List<Task> tl = GetDueTasks(Tasks.ImportantUrgentTasks, asForUtcDate);
            tl.AddRange(GetDueTasks(Tasks.ImportantNotUrgentTasks, asForUtcDate));
            tl.AddRange(GetDueTasks(Tasks.UnimportantUrgentTasks, asForUtcDate));
            tl.AddRange(GetDueTasks(Tasks.UnimportantNotUrgentTasks, asForUtcDate));
            return tl;
        }

        /// <summary>
        /// Get number of all active tasks.
        /// </summary>
        /// <param name="asForUtcDate">the NOW</param>
        /// <returns></returns>
        public int CountActiveTasks(DateTime asForUtcDate)
        {
            Func<Task, bool> isActive = delegate(Task t)
            {
                return !t.Archived
                    && t.Progress < 100
                    && !t.Status.IsFinalStatus
                    && (!t.FinishedAt.HasValue || t.FinishedAt.Value < asForUtcDate);
            };
            return
                Tasks.ImportantUrgentTasks.Count(isActive) +
                Tasks.ImportantNotUrgentTasks.Count(isActive) +
                Tasks.UnimportantNotUrgentTasks.Count(isActive) +
                Tasks.UnimportantUrgentTasks.Count(isActive);
        }

        /// <summary>
        ///  Find the first task meeting criteria.
        /// </summary>
        /// <param name="criteria">conditions</param>
        /// <param name="includeArchived">include archived </param>
        /// <returns>taks or null</returns>
        [System.Diagnostics.DebuggerHidden]
        public Task Find(TaskSearchCriteria criteria, bool includeArchived)
        {
            return criteria.Find(this, includeArchived);
        }

        /// <summary>
        /// Return all tasks meeting criteria.
        /// </summary>
        /// <param name="criteria">conditions</param>
        /// <param name="includeArchived">include archive</param>
        /// <returns>not null, no order of items imposed</returns>
        [System.Diagnostics.DebuggerHidden]
        public TaskObservableCollection<Task> Select(TaskSearchCriteria criteria, bool includeArchived)
        {
            return criteria.Select(this, includeArchived);
        }

        /// <summary>
        /// Sort tasks so that the first will be the one expected to be executed first.
        /// </summary>
        /// <remarks>
        /// Assumes task not finished yet.
        /// Assume if reoccuring, NextOccurence calculated already.
        /// </remarks>
        /// <param name="list">not null</param>
        public static void SortByDue(List<Task> list)
        {
            list.Sort(delegate(Task a, Task b)
            {
                DateTime da = a.DueDate.HasValue ? a.DueDate.Value : DateTime.MaxValue;
                DateTime db = b.DueDate.HasValue ? b.DueDate.Value : DateTime.MaxValue;
                int res = da.CompareTo(db);
                if (res == 0)
                {
                    da = a.Reocurring ? a.RecurrenceDetails.NextOccurence : DateTime.MaxValue;
                    db = b.Reocurring ? b.RecurrenceDetails.NextOccurence : DateTime.MaxValue;
                    res = da.CompareTo(db);
                    if (res == 0)
                    {
                        res = -a.Priority.CompareTo(b.Priority);
                    }
                }
                return res;// BY DueDate ASC, NextOccurence ASC, Priority DESC
            });
        }

        #endregion
        
        #region Protected

        protected static List<Task> GetDueTasks(TaskList list, DateTime asForUtcDate)
        {
            return list.FindAll(delegate(Task t)
            {
                if (t.Archived || t.Status.IsFinalStatus)
                    return false;// task is completed
                if (t.DueDate.HasValue)
                {
                    if (!t.FinishedAt.HasValue || t.FinishedAt.Value.Date <= asForUtcDate)
                        return true;// due and not finished yet (or will be in the future)
                }
                if (t.Reocurring)
                {
                    t.RecurrenceDetails.CalculateNextOccurence(asForUtcDate);
                    if (t.RecurrenceDetails.NextOccurence.Date >= asForUtcDate)
                        return true;// is to be executed in the future
                }
                return false;// unable to determine
            });
        }

        protected void Optimize(TaskList taskList)
        {
            // get rid of instance copies after deserialization
            foreach (Task t in taskList)
            {
                if (t.Status != null)
                    foreach (TaskStatus ts in Statuses)
                        if (!object.ReferenceEquals(t.Status, ts) && t.Status.ID == ts.ID)
                        {
                            t.SuspendChangedEvent();
                            t.Status = ts;
                            t.ResumeChangedEvent();
                        }
                if (t.Flags != null)
                    for (int i = 0; i < t.Flags.Count; i++)
                    {
                        TaskFlag tf = t.Flags[i];
                        Guid id = tf.ID;
                        foreach (TaskFlag f in Flags)
                            if (!object.ReferenceEquals(tf, f) && f.ID == id)
                            {
                                t.Flags.SuspendChangedEvents();
                                t.Flags[i] = f;
                                t.Flags.ResumeChangedEvents();
                            }
                    }
                if (t.CreatedBy != null)
                    foreach (TaskAuthor ta in Authors)
                        if (!object.ReferenceEquals(t.CreatedBy, ta) && t.CreatedBy.ID == ta.ID)
                        {
                            t.SuspendChangedEvent();
                            t.CreatedBy = ta;
                            t.ResumeChangedEvent();
                        }
                if (CreatedBy != null)
                    foreach (TaskAuthor ta in Authors)
                        if (!object.ReferenceEquals(CreatedBy, ta) && CreatedBy.ID == ta.ID)
                        {
                            t.SuspendChangedEvent();
                            CreatedBy = ta;
                            t.ResumeChangedEvent();
                        }
                if (t.Dependencies != null)
                    for (int i = 0; i < t.Dependencies.Count; i++)
                    {
                        Task tt = t.Dependencies[i];
                        Task dupl;
                        if (CheckIsIn(tt, Tasks.ImportantNotUrgentTasks, out dupl) ||
                            CheckIsIn(tt, Tasks.ImportantUrgentTasks, out dupl) ||
                            CheckIsIn(tt, Tasks.UnimportantNotUrgentTasks, out dupl) ||
                            CheckIsIn(tt, Tasks.UnimportantUrgentTasks, out dupl) ||
                            CheckIsIn(tt, Tasks.Archived, out dupl))
                        {
                            System.Diagnostics.Debug.Assert(dupl != null);
                            t.Dependencies.SuspendChangedEvents();
                            t.Dependencies[i] = dupl;
                            t.Dependencies.ResumeChangedEvents();
                        }
                    }
            }
        }

        #endregion

        #region Private

        private static bool CheckIsIn(Task task, TaskList taskList, out Task existing)
        {
            foreach (Task t in taskList)
                if (!object.ReferenceEquals(t, task) && t.ID == task.ID)
                {
                    existing = t;
                    return true;
                }
            existing = null;
            return false;
        }

        #endregion
    }
}